#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <iostream>
#include <cstring>
using namespace std;
/*
通过system V 提供的共享内存来解决全双工

共享原理：
通过在物理内存一段空间在内核空间中映射出一片共享内存端来供不同进程使用
不同进程在得到共享内存的地址后，就可以通过地址来访问共享内存中的数据


注意：
    1、共享内存是多个进程共享同一个内存空间，使用时可能会产生竞态，为了解决这个问题，共享
        内存一般会跟信号量一起使用，完成进程的同步功能
    2、共享内存VS消息队列：消息队列能够保证数据的不丢失性，而共享内存能够保证数据的时效性
    3、对共享内存的读取操作不是一次性的，当读取后，数据依然存放在共享内存中
    4、使用共享内存，跟正常使用指针是一样的，使用时，无需再进行用户空间与内核空间的切换
        了，所以说，共享内存是所有进程间通信方式中效率最高的一种通信方式。



共享内存API:
1.创建key值
    #include <sys/types.h>
    #include <sys/ipc.h>

    key_t ftok(const char *pathname, int proj_id);     // ftok("/", 'k');
        功能：通过给定的文件以及给定的一个随机值，创建出一个4字节整数的key值，用于system V IPC对象的创建
        参数：
            pathname: 一个文件路径，要求是已经存在的文件路径，提供了key值3字节的内容（其中文件的设备号占1字节，文件的inode号占2字节）
            proj_id:  一个随机整数，取后8位（1字节）跟前面的文件共同组成key值，必须是非0的数字
        返回值：
            成功：返回key值
            失败：返回-1并置位错误码


2.通过key值创建共享内存段
    #include <sys/ipc.h>
    #include <sys/shm.h>

    int shmget(key_t key, size_t size, int shmflg);
        功能：申请指定大小的物理内存，映射到内核空间，创建出共享内存段
        参数：
            key:    可以是IPC_PRIVATE，也可以是ftok创建出来的key值
            size:   申请的大小，是一页（4096字节）的整数倍，并且向上取整
            shmflg: 创建标识
                    IPC_CREAT: 创建并打开一个共享内存，如果共享内存已经存在，则直接打开
                    IPC_EXCL:  确保本次创建处理的是一个新的共享内存，如果共享内存已经存在，则报错（错误码为EEXIST）
                    0664：该共享内存的操作权限
                    示例：IPC_CREAT|0664 或 IPC_CREAT|IPC_EXCL|0664
        返回值：
            成功：返回共享内存段的id
            失败：返回-1并置位错误码


3.将共享内存段的地址映射到用户空间
    #include <sys/types.h>
    #include <sys/shm.h>

    void *shmat(int shmid, const void *shmaddr, int shmflg);
        功能：将共享内存段映射到用户空间(引用数+1)
        参数：
            shmid:   共享内存的id号
            shmaddr: 物理内存的起始地址，一般填NULL（由系统自动选择合适的对齐页）
            shmflg:  对共享内存段的操作
                    0：表示读写操作
                    SHM_RDONLY：只读

        返回值：
            成功：返回用于操作共享内存的指针
            失败：返回(void*)-1并置位错误码


4.释放共享内存的映射关系
    int shmdt(const void *shmaddr);
    功能：将进程与共享内存的映射取消(引用数-1)
    参数：
        shmaddr: 共享内存的指针
    返回值：
        成功：返回0
        失败：返回-1并置位错误码


5. 共享内存的控制函数
    #include <sys/ipc.h>
    #include <sys/shm.h>

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    功能：根据给定的不同的cmd执行不同的操作
    参数：
        shmid: 共享内存的ID
        cmd:要操作的指令
            IPC_RMID: 删除共享内存段（第三个参数可以省略）
            IPC_STAT: 获取当前共享内存的属性
            IPC_SET:  设置当前共享内存的属性
        buf:如果参数2为IPC_RMID，则参数3可以省略填NULL
            如果参数2为另外两个，参数3填如下结构体变量
    返回值：
    成功：返回0
    失败：返回 - 1并置位错误码

    详细知识补充：
       shmctl在删除时候是将次共享空间标记为待删除状态，当所有进程都解除与共享内存的映射关系后，
       才会将共享内存段从物理内存中删除，此期间其他新进程无法访问共享内存段
       如果不使用shmctl删除共享内存段，会导致共享内存段一直存在，直到系统重启或者ipcrm -m  id号删除


        结构体定义：
        struct shmid_ds {
            struct ipc_perm shm_perm;   // Ownership and permissions
        size_t shm_segsz;    // Size of segment (bytes)
        time_t shm_atime;    // Last attach time
        time_t shm_dtime;    // Last detach time
        time_t shm_ctime;    // Last change time
        pid_t shm_cpid;      // PID of creator
        pid_t shm_lpid;      // PID of last shmat(2)/shmdt(2)
        shmatt_t shm_nattch; // No. of current attaches
        ...
        };

        第一个成员结构体：
        struct ipc_perm
        {
            key_t __key;          // Key supplied to shmget(2)
            uid_t uid;            // Effective UID of owner
            gid_t gid;            // Effective GID of owner
            uid_t cuid;           // Effective UID of creator
            gid_t cgid;           // Effective GID of creator
            unsigned short mode;  // Permissions + SHM_DEST and SHM_LOCKED flags
            unsigned short __seq; // Sequence number
        };
*/
int main(int agrc, const char *argv[])
{
    // 1.创建key值
    key_t key = ftok("./", 'k');
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key = %#x\n", key);

#define SHM_SIZE 4096 // 定义共享内存的大小

    // 2.创建共享内存段
    int shmid = shmget(key, SHM_SIZE, IPC_CREAT | IPC_EXCL | 0664);
    if (shmid == -1)
    {
        perror("shmget error");
        return -1;
    }
    printf("shmid = %d\n", shmid);

    // 3、将共享内存段映射到用户空间
    char *addr = (char *)shmat(shmid, NULL, 0); // NULL表示让系统自动寻找起始地址  0表示对该共享内存段的操作是读写操作打开
    if (addr == (void *)-1)
    {
        perror("shmat error");
        return -1;
    }
    printf("addr = %p\n", addr); // 输出共享内存段映射的地址

    // 4.对共享内存操作
    while (1)
    {
        printf("请输入要写入的内容：");
        fgets(addr, SHM_SIZE, stdin);
        addr[strlen(addr) - 1] = '\0'; // 去掉换行符
        printf("写入成功\n");
        if (strcmp(addr, "quit") == 0 || strcmp(addr, "q") == 0)
        {
            printf("已停止写入\n\n");
            break;
        }
    }

    // 5.读取
    while (1)
    {
        printf("读取到的内容为：%s\n", addr);
        if (strcmp(addr, "quit") == 0 || strcmp(addr, "q") == 0)
        {
            printf("已停止读取\n");
            break;
        }
    }

    // 6.解除映射关系
    if (shmdt(addr) == -1)
    {
        perror("shmdt error");
        return -1;
    }

    // 7.删除共享内存段
    if (shmctl(shmid, IPC_RMID, NULL) == -1)
    {
        perror("shmctl error");
        return -1;
    }
    printf("共享内存段删除成功\n");
    return 0;
}